#include "test.h"
#include "graph.h"
#include "assets/triples.h"

#define N_LUT 13

static int
_graph_new (LSUP_StoreType type)
{
    const LSUP_StoreInt *sif = LSUP_store_int (type);
    if (sif->setup_fn) sif->setup_fn (NULL, true);

    LSUP_Graph *gr = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), type, NULL, NULL, 0);
    ASSERT (gr != NULL, "Error creating graph!");

    EXPECT_PASS (LSUP_graph_set_uri (gr, LSUP_iriref_new ("urn:gr:1", NULL)));
    EXPECT_STR_EQ (LSUP_graph_uri (gr)->data, "urn:gr:1");

    // Check that setup function is idempotent with clear == false.
    if (sif->setup_fn) EXPECT_INT_EQ (
            sif->setup_fn (NULL, false), LSUP_NOACTION);

    ASSERT (
            strcmp (LSUP_graph_uri (gr)->data, "urn:gr:1") == 0,
            "Graph URI mismatch!");
    EXPECT_INT_EQ (LSUP_graph_size (gr), 0);

    LSUP_graph_free (gr);

    return 0;
}


static int
_graph_add (LSUP_StoreType type)
{
    const LSUP_StoreInt *sif = LSUP_store_int (type);
    if (sif->setup_fn) sif->setup_fn (NULL, true);

    LSUP_Triple *trp = create_triples();

    LSUP_Graph *gr = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), type, NULL, NULL, 0);
    ASSERT (gr != NULL, "Error creating graph!");

    size_t ct;
    LSUP_graph_add (gr, trp, &ct);

    EXPECT_INT_EQ (ct, 8);
    EXPECT_INT_EQ (LSUP_graph_size (gr), 8);

    for (int i = 0; i < sizeof (trp); i++) {
        log_info ("checking triple #%d.", i);
        ASSERT (LSUP_graph_contains (gr, trp + i), "Triple not in graph!");
    }

    LSUP_Triple *missing_trp = LSUP_triple_new (trp[1].s, trp[6].p, trp[4].o);
    ASSERT (! LSUP_graph_contains (gr, missing_trp), "Triple in graph!");
    free (missing_trp);

    free_triples (trp); // gr copied data.

    LSUP_graph_free (gr);

    return 0;
}


static int
_graph_lookup (LSUP_StoreType type)
{
    const LSUP_StoreInt *sif = LSUP_store_int (type);
    LSUP_Triple *trp = create_triples();

    // Lookup triples.
    LSUP_Term *lu_trp[N_LUT][3] = {
        {NULL, NULL, NULL},                 // 8 matches
        {trp[0].s, NULL, NULL},             // 5 matches
        {NULL, trp[2].p, NULL},             // 3 matches
        {NULL, NULL, trp[5].o},             // 2 matches
        {trp[0].s, trp[0].p, NULL},         // 1 match
        {NULL, trp[0].p, trp[0].o},         // 1 match
        {trp[0].s, trp[2].p, trp[5].o},     // 1 match
        {trp[0].p, NULL, NULL},             // 0 matches
        {NULL, trp[2].s, NULL},             // 0 matches
        {NULL, NULL, trp[5].p},             // 0 matches
        {trp[2].s, trp[6].p, NULL},         // 0 matches
        {NULL, trp[1].p, trp[5].o},         // 0 matches
        {trp[2].s, trp[2].p, trp[5].o},     // 0 matches
    };

    // Lookup result counts.
    size_t lu_ct[N_LUT] = {
        8,
        5, 3, 2,
        1, 1, 1,
        0, 0, 0,
        0, 0, 0
    };

    /* TODO
    // Index of lookup matches from trp.
    size_t lu_match[N_LUT][8] = {
        {0, 1, 2, 3, 4, 5, 6, 7},
        {0, 3, 4, 5, 7}, {2, 4, 7}, {5, 7},
        {0}, {0}, {7},
        {}, {}, {},
        {}, {}, {},
    };

    // Index of lookup non-matches from trp.
    size_t lu_no_match[N_LUT][8] = {
        {},
        {1, 2, 6}, {0, 1, 3, 5, 6}, {0, 1, 2, 3, 4, 6},
        {1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6},
        {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7},
        {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7},
    };
    */

    if (sif->setup_fn) sif->setup_fn (NULL, true);

    LSUP_Graph *gr = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), type, NULL, NULL, 0);

    size_t ct;
    LSUP_graph_add (gr, trp, &ct);

    EXPECT_INT_EQ (ct, 8);
    EXPECT_INT_EQ (LSUP_graph_size (gr), 8);

    for (int i = 0; i < N_LUT; i++) {
        log_info ("Checking triple #%d on %d.", i, type);
        LSUP_GraphIterator *it = LSUP_graph_lookup (
                gr, lu_trp[i][0], lu_trp[i][1], lu_trp[i][2], &ct);
        EXPECT_INT_EQ (ct, lu_ct[i]);

        /* TODO
        for (int j = 0; j < 8; j++) {
            for (int k = 0; LSUP_graph_iter_next(it) != LSUP_END; k++) {
                ASSERT (
                        LSUP_graph_contains (trp[lu_match[j]]),
                        "Triple not found!");
                ASSERT (
                        !(LSUP_graph_contains (trp[lu_no_match[j]])),
                        "Unexpected triple found!");
            }
        }
        */

        LSUP_graph_iter_free (it);
    };

    free_triples (trp);
    LSUP_graph_free (gr);

    return 0;
}


static int
_graph_remove (LSUP_StoreType type)
{
    const LSUP_StoreInt *sif = LSUP_store_int (type);
    if (sif->setup_fn) sif->setup_fn (NULL, true);

    LSUP_Triple *trp = create_triples();

    LSUP_Graph *gr = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), type, NULL, NULL, 0);

    size_t ct;
    LSUP_graph_add (gr, trp, &ct);

    EXPECT_INT_EQ (ct, 8);
    EXPECT_INT_EQ (LSUP_graph_size (gr), 8);

    // Triples 0, 3, 4, 5, 7 will be removed.
    LSUP_graph_remove (gr, trp[0].s, NULL, NULL, &ct);
    EXPECT_INT_EQ (ct, 5);
    EXPECT_INT_EQ (LSUP_graph_size (gr), 3);

    ASSERT (!LSUP_graph_contains (gr, trp + 0), "Unexpected triple found!");
    ASSERT (LSUP_graph_contains (gr, trp + 1), "Triple not in graph!");
    ASSERT (LSUP_graph_contains (gr, trp + 2), "Triple not in graph!");
    ASSERT (!LSUP_graph_contains (gr, trp + 3), "Unexpected triple found!");
    ASSERT (!LSUP_graph_contains (gr, trp + 4), "Unexpected triple found!");
    ASSERT (!LSUP_graph_contains (gr, trp + 5), "Unexpected triple found!");
    ASSERT (LSUP_graph_contains (gr, trp + 6), "Triple not in graph!");
    ASSERT (!LSUP_graph_contains (gr, trp + 7), "Unexpected triple found!");

    free_triples (trp); // gr copied data.

    LSUP_graph_free (gr);

    // TODO Test complete removal of triples from index when they are not
    // in another context.

    return 0;
}


static int
test_environment()
{
    // The env should already be initialized and re-initializing it is idempotent.
    EXPECT_INT_EQ (LSUP_IS_INIT, true);
    ASSERT (LSUP_init() > 0, "Error initializing environment!");
    EXPECT_INT_EQ (LSUP_IS_INIT, true);

    // Tearing down is idempotent too.
    LSUP_done();
    EXPECT_INT_EQ (LSUP_IS_INIT, false);
    LSUP_done();
    EXPECT_INT_EQ (LSUP_IS_INIT, false);

    ASSERT (LSUP_init() >= 0, "Environment not initialized!");
    EXPECT_INT_EQ (LSUP_IS_INIT, true);
    ASSERT (LSUP_init() >= 0, "Environment not initialized!");
    EXPECT_INT_EQ (LSUP_IS_INIT, true);

    return 0;
}


static int test_graph_new() {
#define ENTRY(a, b) \
    if (_graph_new (LSUP_STORE_##a) != 0) return -1;
BACKEND_TBL
#undef ENTRY

    return 0;
}


static int test_graph_add() {
#define ENTRY(a, b) \
    if (_graph_add (LSUP_STORE_##a) != 0) return -1;
BACKEND_TBL
#undef ENTRY

    return 0;
}


static int test_graph_lookup() {
#define ENTRY(a, b) \
    if (_graph_lookup (LSUP_STORE_##a) != 0) return -1;
BACKEND_TBL
#undef ENTRY

    return 0;
}


static int test_graph_remove() {
#define ENTRY(a, b) \
    if (_graph_remove (LSUP_STORE_##a) != 0) return -1;
BACKEND_TBL
#undef ENTRY

    return 0;
}


static int test_graph_copy()
{
    LSUP_Triple *trp = create_triples();

    LSUP_Graph *gr1 = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), LSUP_STORE_HTABLE, NULL, NULL, 0);
    ASSERT (gr1 != NULL, "Error creating graph!");

    LSUP_graph_add (gr1, trp, NULL);

    // Copy to graph with same store type.
    LSUP_Graph *gr2 = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), LSUP_STORE_HTABLE, NULL, NULL, 0);
    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr2));
    EXPECT_INT_EQ (LSUP_graph_size (gr1), LSUP_graph_size (gr2));

    for (int i = 0; i < sizeof (trp); i++) {
        log_info ("checking triple #%d.", i);
        ASSERT (
                LSUP_graph_contains (gr2, trp + i),
                "Triple not in copied graph!");
    }

    // Copy to graph with a different store type.
    LSUP_Graph *gr3 = LSUP_graph_new (
            LSUP_iriref_new (NULL, NULL), LSUP_STORE_MDB, NULL, NULL, 0);
    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr3));
    EXPECT_INT_EQ (LSUP_graph_size (gr1), LSUP_graph_size (gr2));

    for (int i = 0; i < sizeof (trp); i++) {
        log_info ("checking triple #%d.", i);
        ASSERT (
                LSUP_graph_contains (gr3, trp + i),
                "Triple not in copied graph!");
    }

    LSUP_graph_free (gr3);
    LSUP_graph_free (gr2);
    LSUP_graph_free (gr1);
    free_triples (trp);

    return 0;
}


int graph_tests()
{
    RUN (test_environment);
    RUN (test_graph_new);
    RUN (test_graph_add);
    RUN (test_graph_lookup);
    RUN (test_graph_remove);
    RUN (test_graph_copy);

    return 0;
}

